package bloodfang;
import java.util.ArrayList;

import bloodfang.CycleCombat2Phase.PhaseName;
import bloodfang.data.KeyValueStore;


public class CycleCombat2 extends CycleRogue {
	
	private double current_ks_cd = killing_spree_cd;
	private double current_ar_cd = adrenaline_rush_cd;
	private double current_vanish_cd = vanish_cd;
	private double avg_bg_buff_eviscerate;
	private double avg_bg_buff;
	private final double rvs_duration = 24.;
	private double ar_duration = 15.;
	private final double ks_duration = 3.;
	
	public CycleCombat2(Stats stats,Attacks attacks){
		this.stats = stats;
		this.attacks = attacks;
		this.stats.getSettings().spec = Spec.COMBAT;
	}

	protected void generate_aps(){
		double last_aps = 0.;
		aps.clear();
		
		double base_ks_cd = killing_spree_cd;
		double base_ar_cd = adrenaline_rush_cd;
		
		double cooldownRecoveryRate = stats.getCooldownRecoveryRate();
		base_ks_cd /= (1. + cooldownRecoveryRate);
		base_ar_cd /= (1. + cooldownRecoveryRate);

		current_ks_cd = base_ks_cd + stats.getSettings().cooldown_delay;
		current_ar_cd = base_ar_cd + stats.getSettings().cooldown_delay;
//		System.out.println("ar_cd " + current_ar_cd);
		if(stats.hasGlyph("disappearance")) {
			current_vanish_cd = vanish_cd - 60.; 
		}
		
		while(true){
			aps.clear(); // do not remove
			
			// global energy_regen which gets modified by globally used attacks
			// e.g. revealing_strike
			double energy_regen = get_energy_regen();

			// we are having 2 different cycles to manage
			// 1. adrenaline rush where we are getting just more energy
			// 2. no adrenaline rush, the normal rotation
			ar_duration = 15.;
			
			// basically energy regen get doubled during adrenaline rush 
			double ar_energy_regen = 12. * (1. + 0.01 * stats.getBuffed(Stat.hst) / RATING.HASTE_CONVERSION_RATING);
			
//			double pooling_time = pooling_time(energy_regen, 75.);
//			double extra_regen = 0;
//			if(pooling_time > 0){
//				extra_regen = energy_regen * pooling_time;
//				System.out.println(extra_regen);
//			}
			
			CycleCombatSetup ar_cycle = new CycleCombatSetup();
			ar_cycle.adrenaline_rush = true;
			ar_cycle.length = ar_duration;
			ar_cycle.energy_regen = energy_regen + ar_energy_regen;
			
			CycleCombatSetup normal_cycle = new CycleCombatSetup();
			normal_cycle.adrenaline_rush = false;
			normal_cycle.length = current_ar_cd - ar_duration;
			normal_cycle.energy_regen = energy_regen;
			
			// pre calc of cycles
			KeyValueStore template_ks_cycle_aps = get_ks_cycle_aps(energy_regen);
			KeyValueStore template_ar_cycle_aps = get_cycle_aps(ar_cycle);
			KeyValueStore template_normal_cycle_aps = get_cycle_aps(normal_cycle);
			
			ArrayList<CycleCombat2Phase> phases = new ArrayList<CycleCombat2Phase>();
			double time_left_fight = 360.; //stats.getSettings().FIGHT_LENGTH;
			double active_ar_cd = 0.;
			double active_ks_cd = 0.;
			while(time_left_fight > 0.0001){
				//System.out.println(time_left_fight);
				if(active_ks_cd <= 0.001){
//					System.out.println("perform ks " + time_left_fight);
//					System.out.println("actual cd " + active_ks_cd);
					active_ks_cd = current_ks_cd;
					active_ks_cd -= ks_duration;
					CycleCombat2Phase phase_ks = new CycleCombat2Phase(PhaseName.ks);
					double length = 3.;
					if(length > time_left_fight)
						length = time_left_fight;
					phase_ks.time_mark = time_left_fight;
					phase_ks.length = length;
					phases.add(phase_ks);
					time_left_fight -= ks_duration;
				}
				else if(active_ar_cd <= 0.001){
//					System.out.println("perform ar " + time_left_fight);
					active_ar_cd = current_ar_cd;
					KeyValueStore ar_cycle_aps = template_ar_cycle_aps;
					active_ar_cd -= ar_duration * get_real_cd_reduce_per_sec(ar_cycle_aps);
					active_ks_cd -= ar_duration * get_real_cd_reduce_per_sec(ar_cycle_aps);
					double length = ar_duration;
					if(length > time_left_fight)
						length = time_left_fight;
					CycleCombat2Phase phase = new CycleCombat2Phase(PhaseName.ar);
					phase.time_mark = time_left_fight;
					phase.length = length;
					phases.add(phase);
					time_left_fight -= length;
				}else{
//					System.out.println("perform normal " + time_left_fight);
					KeyValueStore normal_cycle_aps = template_normal_cycle_aps;
					double ks_to_finish = get_new_cd(active_ks_cd, normal_cycle_aps);
					double ar_to_finish = get_new_cd(active_ar_cd, normal_cycle_aps);
					//System.out.println("cds: ks: " + ks_to_finish);
					double length = Math.min(ks_to_finish, ar_to_finish);

					active_ar_cd -= length * get_real_cd_reduce_per_sec(normal_cycle_aps);
					active_ks_cd -= length * get_real_cd_reduce_per_sec(normal_cycle_aps);
					if(length > time_left_fight)
						length = time_left_fight;
					//System.out.println(active_ks_cd);
					CycleCombat2Phase phase3 = new CycleCombat2Phase(PhaseName.normal);
					phase3.time_mark = time_left_fight;
					phase3.length = length;
					phases.add(phase3);
					time_left_fight -= length;
				}
			}
			
			//System.out.println("start");
			double all_phase_length = 0;
			double time_between_ks = 0;
			double time_between_ar = 0;
			double ks = 0;
			double ar = 0;
			for(CycleCombat2Phase p : phases){
//				System.out.println(all_phase_length + " " + p.name);
				if(p.name == PhaseName.ks){
					if(ks == 0)
						ks = all_phase_length;
					else{
						time_between_ks = Math.max(time_between_ks, Math.abs(all_phase_length - ks));
						//System.out.println("time between ks " + time_between_ks);
						ks = all_phase_length;
					}
				}else if(p.name == PhaseName.ar){
					if(ar == 0)
						ar = all_phase_length;
					else{
						time_between_ar = Math.max(time_between_ar, Math.abs(all_phase_length - ar));
						//System.out.println("time between ar " + time_between_ar);
						ar = all_phase_length;
					}
				}
				all_phase_length += p.length;
			}
			double cycle_duration = time_between_ar + ks_duration;
			double normal_duration = cycle_duration - ks_duration - ar_duration;
//			System.out.println("----");
//			System.out.println("all phase length " + all_phase_length);
//			System.out.println("real_ks_cd " + time_between_ks);
//			System.out.println("real_ar_cd " + time_between_ar);
//			System.out.println("dur normal rota " + normal_duration);
			
			for(String attack : template_ks_cycle_aps.keySet()){
				aps.add(attack, template_ks_cycle_aps.get(attack) * (ks_duration/cycle_duration));
			}
			
			for(String attack : template_ar_cycle_aps.keySet()){
				aps.add(attack, template_ar_cycle_aps.get(attack) * (ar_duration/cycle_duration));
			}
			
			for(String attack : template_normal_cycle_aps.keySet()){
				aps.add(attack, template_normal_cycle_aps.get(attack) * (normal_duration/cycle_duration));
			}
			
//			for(CycleCombat2Phase p : phases){
//				for(String attack : p.aps.keySet()){
//					aps.add(attack, p.aps.get(attack) * (p.length/all_phase_length));
//				}
//			}
			
			double mh_aps = aps.get("mh") + aps.get("sinister_strike") + aps.get("revealing_strike") + aps.get("eviscerate") + aps.get("ks_mh");
			double mg_aps = mh_aps * this.stats.getMhStyleHitChance() * this.stats.getMainGaucheProcChance();
			aps.add("main_gauche", mg_aps);
			double oh_aps = aps.get("oh") + aps.get("ks_oh") + aps.get("main_gauche");
			
			double ip_aps = (mh_aps + mg_aps) * 0.3;
			ip_aps += oh_aps * 0.3;
			
			aps.add("instant_poison", ip_aps);
			// perk instant poison
			if(stats.getSettings().player_level < 100) {
				aps.add("deadly_poison", 1./3.);
			}
			
			this.procs(mh_aps, oh_aps, ip_aps);
			
			if(Math.abs(last_aps - aps.sum()) < PRECISION)
				break;
			last_aps = aps.sum();
						
//			System.out.println("current_ks_cd " + current_ks_cd);
			
			double bg_singleduration = 1 / aps.get("sinister_strike") * 4;
			double bg_length = 15 + 3 * bg_singleduration;
			double bg_uptime_0_10_20 = bg_singleduration / bg_length;
			double bg_uptime_30 = 15 / bg_length;
			// this is for every other attack
			// optimization would be to save for every single attack
			// e.g every single sinister strike which buffs are active
			// but that leads to an simulation
			double dmg_buff_deep_insight = 0.3;
			if(stats.getSettings().player_level >= 100)
				dmg_buff_deep_insight += 0.2; // perk combat Empowered Bandits Guile
			avg_bg_buff = (0.0 + 0.1 + 0.2) * bg_uptime_0_10_20 + dmg_buff_deep_insight * bg_uptime_30;
			// with anticipation we can shift 1 evi from the 0 % buff to an 30% buff
			// so we assume that every 0% evi is with anticipation an 30% evi
			avg_bg_buff_eviscerate = avg_bg_buff;
			if(stats.hasTalent(Talent.anticipation)){
				avg_bg_buff_eviscerate += dmg_buff_deep_insight * bg_uptime_30;
			}
		}
	}
	
	private KeyValueStore get_cycle_aps(CycleCombatSetup cycle){
		KeyValueStore aps = new KeyValueStore();
		double total_speed = this.calc_total_speed();
		if(cycle.adrenaline_rush)
			total_speed *= 1.2; // ar attack speed
		
		double mh_hits_per_sec = 0.;
		double oh_hits_per_sec = 0.;
		double mh_swing_per_sec = total_speed / stats.getMh().getSpeed();
		double oh_swing_per_sec = total_speed / stats.getOh().getSpeed();
		mh_hits_per_sec = mh_swing_per_sec * this.stats.getMhHitChance();
		oh_hits_per_sec = oh_swing_per_sec * this.stats.getOhHitChance();
		aps.add("mh", mh_hits_per_sec);
		aps.add("oh", oh_hits_per_sec);


		//System.out.println("mh" + mh_hits);
		//System.out.println("oh" + oh_hits);
		
		// lets find out how much energy we get from main_gauche
		double proc_chance_oh = stats.getOh().getSpeed()/1.4 * 0.2;
		double proc_chance_mh = 0.2;
		double proc_chance_mg = stats.getMainGaucheProcChance() * stats.getMhStyleHitChance();
		
		// and combat potency
		double cp_regen = oh_hits_per_sec * proc_chance_oh * 15.;
		cp_regen += mh_hits_per_sec * proc_chance_mg * proc_chance_mh * 15.;
		double mg_regen = cp_regen;
		
		double active_energy_regen = cycle.energy_regen + mg_regen;
		//active_energy_regen += cycle.flat_extra_energy_regen;
		
		double finisher_size = 5.;
		double avg_relentless_strikes = 5. * finisher_size;
		double ss_cp = 1. + 0.2;
		if(stats.hasSetBonus(SetBonus.T17_2PC)) {
			ss_cp += 0.1;
		}
		double ruthlessness = finisher_size * 0.2;
		
		// rvs is used every 24sec to be on 100% uptime
		double ec_rvs = stats.getRealEnergyCost(40);
		double rvs_per_sec = 1. / this.rvs_duration;
		aps.add("revealing_strike", rvs_per_sec);
		active_energy_regen -= rvs_per_sec * ec_rvs;
		double extra_evis_per_sec = rvs_per_sec * 1 / (finisher_size - ruthlessness);
		// rvs_end
		
		// vanish
		double ec_ambush = stats.getRealEnergyCost(60);
		if(stats.hasTalent(Talent.shadow_focus))
			ec_ambush *= 0.25;
		double vanish_per_sec = 1. / current_vanish_cd;
		aps.add("ambush", vanish_per_sec);
		active_energy_regen -= vanish_per_sec * ec_ambush;
		extra_evis_per_sec += vanish_per_sec * 2 / (finisher_size - ruthlessness);
		
		active_energy_regen -= extra_evis_per_sec * (stats.getRealEnergyCost(35) - avg_relentless_strikes);

		if(stats.hasSetBonus(SetBonus.T17_4PC)) {
			// 25% chance to get another evi for free results in 25% more evis
			extra_evis_per_sec = extra_evis_per_sec * 0.25;
		}
		
		double avg_cpg_per_finisher = (finisher_size - ruthlessness) / ss_cp;
				
		double base_ec_ss = 50;
		if(stats.hasSetBonus(SetBonus.T16_2PC))
			base_ec_ss -= 15 * 0.2;
		double ec_ss = stats.getRealEnergyCost(base_ec_ss);
		double ec_snd = 25;
		double ec_evi = stats.getRealEnergyCost(35);
		
		double snd_duration = 6 + 6 * finisher_size;
		if(stats.hasSetBonus(SetBonus.T15_2PC)){
			snd_duration += 6;
		}
		
		double energy_cost = avg_cpg_per_finisher * ec_ss;
		double energy_cost_evi = energy_cost + ec_evi - avg_relentless_strikes;
		double energy_cost_snd = energy_cost + ec_snd - avg_relentless_strikes;
		
		double snds = 1 / snd_duration;
		if(stats.hasSetBonus(SetBonus.T17_4PC)) {
			// 25% chance to get an evi for free after snd
			extra_evis_per_sec += snds * 0.25;
		}
		double energy_regen_left_for_evi = active_energy_regen;
		energy_regen_left_for_evi -= snds * energy_cost_snd;
		
		double ss_base = snds * avg_cpg_per_finisher;
		double aps_so_far = snds + ss_base + extra_evis_per_sec;
		double gcd = 1.0;
		if(cycle.adrenaline_rush) {
			gcd -= 0.2;
			if(stats.getSettings().player_level >= 100) {
				gcd -= 0.3; // perk combat Enhanced Adrenaline Rush
			}
		}
		gcd += stats.getSettings().gcd_latency;
		
		double aps_free = 1. / gcd;
		double aps_left = aps_free - aps_so_far;
		double evis_per_sec = energy_regen_left_for_evi / energy_cost_evi;
		double evis_build_up_aps = evis_per_sec * (1 + avg_cpg_per_finisher);
		double ss_per_sec = evis_per_sec * avg_cpg_per_finisher;
		if(stats.hasSetBonus(SetBonus.T17_4PC)) {
			// 25% chance to get another evi for free results in 25% more evis
			extra_evis_per_sec += evis_per_sec * 0.25;
		}

		if(aps_left < evis_build_up_aps){
			double mod = evis_build_up_aps / aps_left;
//			System.out.println(mod);
//			System.out.println(aps_left + "<" + evis_build_up_aps);
			
			ss_per_sec = ss_per_sec / mod;
			evis_per_sec = evis_per_sec / mod;
			
//			double energy_lost = energy_regen_left_for_evi - evis_per_sec * energy_cost_evi;
//			System.out.println(cycle.adrenaline_rush + " " + energy_lost);
		}
		ss_per_sec += ss_base;
		aps.add("sinister_strike", ss_per_sec);
		aps.add("eviscerate", evis_per_sec + extra_evis_per_sec);
		return aps;
	}
	
	private KeyValueStore get_ks_cycle_aps(double energy_regen){
		KeyValueStore aps = new KeyValueStore();
		double total_speed = this.calc_total_speed();
		
		double mh_hits_per_sec = 0.;
		double oh_hits_per_sec = 0.;
		double mh_swing_per_sec = total_speed / stats.getMh().getSpeed();
		double oh_swing_per_sec = total_speed / stats.getOh().getSpeed();
		mh_hits_per_sec = mh_swing_per_sec * this.stats.getMhHitChance();
		oh_hits_per_sec = oh_swing_per_sec * this.stats.getOhHitChance();
		aps.add("mh", mh_hits_per_sec);
		aps.add("oh", oh_hits_per_sec);
		
		double ks_aps = 7. / 3.;
		
		// lets find out how much energy we get from main_gauche
		double proc_chance_oh = stats.getOh().getSpeed() / 1.4 * 0.2;
		double proc_chance_mh = 0.2;
		double proc_chance_mg = stats.getMainGaucheProcChance() * stats.getMhStyleHitChance();
		// and combat potency
		double cp_regen = oh_hits_per_sec * proc_chance_oh * 15.;
		cp_regen += (mh_hits_per_sec + ks_aps) * proc_chance_mg * proc_chance_mh * 15.;
		double mg_regen = cp_regen;
		
		double active_energy_regen = energy_regen + mg_regen;
		
//		System.out.println(active_energy_regen*3);
		
		aps.add("ks_mh", ks_aps);
		aps.add("ks_oh", ks_aps);
		
//		System.out.println("ks");
//		aps.print();
		
		return aps;
	}
	
	private double get_real_cd_reduce_per_sec(KeyValueStore aps){
		double avg_reduction = 2 * 5.;
		double time_between_finisher = 1 / aps.get("eviscerate");
		double real_cd_reduce_per_sec = 1 + avg_reduction / time_between_finisher;
		return real_cd_reduce_per_sec;
	}
	
	private double get_new_cd(double base_cd, KeyValueStore aps){
		double aps_finisher = aps.get("eviscerate");
		double avg_reduction = 2 * 5.;
		double final_cd = base_cd * (1 - avg_reduction / (1. / aps_finisher + avg_reduction));
		return final_cd;
	}

	@Override
	protected double getSpecDamageMod(AttackData ab) {
		double dmgMod = 1.;
		if(ab.getAttack().equals("eviscerate")){
			dmgMod *= (1 + this.avg_bg_buff_eviscerate);
		}else
			dmgMod *= (1 + this.avg_bg_buff);
		return dmgMod;
	}
	
	@Override
	protected double getBaseEnergyRegen() {
		double venom_zest = stats.hasTalent(Talent.venom_zest) ? 0.05 : 0;
		return 10 * (1 + 0.2 + venom_zest);
	}
}
